---------------------------------------- -- G.I. Joe for NES password system -- -- -- -- by Disch! -- ---------------------------------------- I looked into this stuff by request for someone on IRC. Passwords are fun! Passwords are 9 digits. The first 8 digits contain the data in a scrambled fasion. I did not analyze what the data does, but it's probably stuff like character levels, which mission is next, etc. The last digit is a checksum digit that prevents random crap from being valid passwords. Each password digit is a value between 00-1E inclusive. In-game, these values are represented by numerical digits '0'-'9' and capital letters (except for vowels) 0-9 -> $00-$09 B-D -> $0A-$0C F-H -> $0D-$0F J-N -> $10-$14 P-T -> $15-$19 V-Z -> $1A-$1E There are four descrambling tables... only one of which is used for any paticular password. The table to use is determined by the low 2 bits of digit 9 of the password. Note that those 2 bits are ALSO used for the checksum, so that means the result of the checksum directly determines which descrambling LUT to use. Password digits that do not exist in the descrambling LUT will cause the password to be invalid. Hard to explain this in words -- so here's an example. Given this descrambling LUT: 03 18 15 14 05 1C 0E 1E 17 0C 0A 02 00 0F 1A 08 <--- scrambled values 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F <--- index The following indeces: 05 00 06 08 would be scrambled to: 1C 03 0E 17 and the following password: 02 18 08 05 would be descrambled to these indeces: 0B 01 0F 04 But that's only HALF of the descrambling. The index of the password digit is then subtracted from the LUT index to get the ACTUAL descrabled byte (only the low 4 bits of the result is used). To recap -- descrambling: 1) Run password digits through LUT to find the LUT index 2) Subtract password index from LUT index to get 'N' 3) Clip 'N' at 4 bits to get descrambled byte For an example. Using the above listed descrambling LUT: 17 05 1A 08 1C 1A 18 1E <---- password 08 04 0E 0F 05 0E 01 07 <---- LUT index -0 -1 -2 -3 -4 -5 -6 -7 <---- values to subtract 08 03 0C 0C 01 09 0B 00 <---- descrambled data The 9th digit (the checksum digit) is never run through the descrambler -- and is always used as-is. I should also note that the checksum digit is also never larger than 4-bits. This means that no valid password will ever have a last digit higher than 'H'. The only exceptions are the 5 "special" passwords which all begin and end with 'Z' -- as those are handled specially and are not run through the password decryptor. There are 4 descrambling LUTs. They are: 0 -> 03 18 15 14 05 1C 0E 1E 17 0C 0A 02 00 0F 1A 08 1 -> 1A 1E 0C 03 09 14 05 1C 18 0E 08 0A 02 17 00 16 2 -> 0F 03 06 09 14 05 1C 0E 1E 17 0C 1A 08 00 0A 02 3 -> 1E 17 0A 10 00 1A 0C 08 05 0F 03 18 09 14 1C 0E The checksum is built from the LUT indeces (note: NOT the descrambled byte). Each of the 8 4-bit values are summed, and the value is left shifted with a 4-bit circular shifter after each addition. Byte 0 is added first, then 1, then 2, etc. Since the shifting is 4-bit circular, the checksum is only 4 bits. What the hell does 4-bit circular shifting mean? Pretty simple... you shift the values left 1 bit, and the value that "comes out" the left "goes in" the right. Example: 0010 shifts to 0100 0100 shifts to 1000 1000 shifts to 0001 0001 shifts to 0010 etc So given the following 3 indeces: 05 07 0A. To calculate the checksum, you'd do the following (checksum in parenthesis): - add 05 ($05) - clip to 4 bits ($05) - circular shift ($0A) - add 07 ($11) - clip to 4 bits ($01) - circular shift ($02) - add 0A ($0C) - clip to 4 bits ($0C) - circular shift ($09) Meaning the resulting checksum would be $09 (which would mean you must use "table 1" to scramble the bytes -- remember the low 2 bits of the checksum determines the scrambling table to use) Of course for a real password you'd sum 8 indeces in that fashion since there are 8 digits. But you just keep doing that same pattern. Forming the password is pretty simple... you just do the process in reverse. Given the following descrambled string: 02 04 07 08 0A 05 00 01 <--- raw, descrambled string +0 +1 +2 +3 +4 +5 +6 +7 <--- add the digit index 02 05 09 0B 0E 0A 06 08 <--- LUT indeces -- need to compute checksum here to know which scrambling LUT to use -- +02 (02) shift (04) +05 (09) shift (03) +09 (0C) shift (09) +0B (04) shift (08) +0E (06) shift (0C) +0A (06) shift (0C) +06 (02) shift (04) +08 (0C) shift (09) 09 <--- checksum. Low 2 bits are '01' -- use scrambling table 1: 1A 1E 0C 03 09 14 05 1C 18 0E 08 0A 02 17 00 16 <-- scrambling table 1 0C 14 0E 0A 00 08 05 18 09 <--- scrambled password (with checksum) password is: DNGB085S9 Try it! It works! Each of the 8 password digits represents 4 bits of descrambled, raw data. Byte 0 (first password digit): can be anywhere from $01 to $06. Higher or lower values will result in an invalid password. Byte 1 (second digit): low 2 bits are ignored/discarded. High bits can be %00, %01, or %10, but cannot be %11 or the password will be invalid. The remaining bytes: Any value is valid. Low 2 bits seem to be used for something other than the high 2 bits. I'm not going to analyze what each of these things do... personally I don't really care =P. I was only interested in the scrambling/checksum portion of it. The rest can be figured by putting in different values and seeing what is different in the game (and that'd be easier for someone who is more familiar with the game than I) There are also 5 "special" passwords which do not get run through this decryption process. These passwords are hardcoded in the game and do special things... they are: 1E 12 0E 13 1E 12 0E 13 1E -- ZLGMZLGMZ -- 3 members, Leader Duke, maximum level 1E 18 0D 1C 1E 18 0D 1C 1E -- ZSFXZSFXZ -- 2 members, Leader Duke, maximum level 1E 11 14 1E 1E 12 0A 0A 1E -- ZKNZZLBBZ -- 2 members, Leader Duke, maximum level 1E 13 18 0B 1E 19 18 19 1E -- ZMSCZTSTZ -- Sound test 1E 01 18 19 1E 14 0C 0E 1E -- Z1STZNDGZ -- Ending sequence ===================================================== ===================================================== ===================================================== ===================================================== ===================================================== ===================================================== And now that the format is outlined -- here is some disassembly that I made and used to discover above info. Yes, these addresses are correct... the code is copied to RAM and then jumped to. Password is stored at $0780-$0788 in RAM. ; Check to see if password is in special LUT 000003D2: A900 LDA #$00 ; start at zero 000003D4: 8530 STA $30 ; write A to LUT index var 000003D6: 0A ASL A 000003D7: 0A ASL A 000003D8: 0A ASL A ; multiply index by 8 000003D9: 6530 ADC $30 ; add it again (mutiplying by 9 -- size of 1 password) 000003DB: A8 TAY ; put in Y 000003DC: A200 LDX #$00 ; set X to zero ; this is a loop that checks to see if password matches ; special password in LUT 000003DE: BD8007 LDA $0780,X ; get password digit 000003E1: D9B505 CMP $05B5,Y ; compare to special LUT 000003E4: D05E BNE $5E->0444 ; if they don't match, skip ahead to 0444 000003E6: C8 INY ; they do match, so inc X,Y to look at next byte 000003E7: E8 INX 000003E8: E009 CPX #$09 ; loop until X=9 000003EA: D0F2 BNE $F2->03DE ; jump back to loop ; if code got here, it matched the special password ; $30 contains the index ($00-04) of the special password to use 000003EC: A205 LDX #$05 000003EE: BDB4ED LDA $EDB4,X 000003F1: 9D5006 STA $0650,X 000003F4: 9D4906 STA $0649,X 000003F7: A900 LDA #$00 000003F9: 9D4306 STA $0643,X 000003FC: CA DEX 000003FD: 10EF BPL $EF ; $03EE 000003FF: A901 LDA #$01 00000401: 8DFB07 STA $07FB 00000404: 8DFC07 STA $07FC 00000407: A530 LDA $30 00000409: C903 CMP #$03 0000040B: B023 BCS $23 ; $0430 0000040D: A530 LDA $30 0000040F: 8572 STA $72 00000411: A205 LDX #$05 00000413: A90C LDA #$0C 00000415: 9D4306 STA $0643,X 00000418: CA DEX 00000419: 10F8 BPL $F8 ; $0413 0000041B: A963 LDA #$63 0000041D: 8D2707 STA $0727 00000420: A963 LDA #$63 00000422: 8D2807 STA $0728 00000425: AD2E07 LDA $072E 00000428: 0901 ORA #$01 0000042A: 8D2E07 STA $072E 0000042D: 4CE504 JMP $04E5 00000430: D003 BNE $03 ; $0435 00000432: 4CE504 JMP $04E5 00000435: A205 LDX #$05 00000437: A900 LDA #$00 00000439: 9D4306 STA $0643,X 0000043C: CA DEX 0000043D: 10F8 BPL $F8 ; $0437 0000043F: 8572 STA $72 00000441: 4CE504 JMP $04E5 ; code jumps here if digit does not match special password LUT 00000444: E630 INC $30 ; increment LUT index 00000446: A530 LDA $30 ; (also put LUT index in A -- note it gets STA'd after the branch) 00000448: C905 CMP #$05 ; if LUT index does not equal 5 yet (there are 5 special passwords)... 0000044A: D088 BNE $88->03D4; jump back to special password checking ; code reaches here if none of the special passwords matched ; IE: this is the start of the -actual- password decryptor 0000044C: AD8807 LDA $0788 ; get last digit of password 0000044F: C910 CMP #$10 ; make sure it's less than #$10 00000451: 9003 BCC $03->$0456 ; if >= #$10... 00000453: 4C6D05 JMP $056D ; jump to $056D (bad password) 00000456: 2903 AND #$03 ; otherwise (less than #$10) 00000458: 0A ASL A ; get the low 2 bits 00000459: 0A ASL A 0000045A: 0A ASL A 0000045B: 0A ASL A ; multiply by 16 0000045C: 6975 ADC #$75 0000045E: 8514 STA $14 ; $14,$15 becomes a pointer (hereon "scramble LUT") 00000460: A900 LDA #$00 ; points to $0575+N 00000462: 6905 ADC #$05 ; where N is the low 2 bits of the last password digit * 16 00000464: 8515 STA $15 ; so I guess this means there's 4 16-byte LUTs starting at $0575 00000466: A200 LDX #$00 ; 0->X X is used to index our password 00000468: 8611 STX $11 ; 0->$11 $11 is the checksum 0000046A: A000 LDY #$00 ; 0->Y Y is used to index scramble LUT ; loop for running each password digit through the scramble LUT 0000046C: BD8007 LDA $0780,X ; get password current digit 0000046F: D114 CMP ($14),Y ; compare it to LUT entry 00000471: F008 BEQ $08->047B ; jump ahead if matches 00000473: C8 INY ; if not a match, inc Y to look at next LUT entry 00000474: C010 CPY #$10 ; but don't go back end of LUT 00000476: D0F4 BNE $F4->046C ; loops back 00000478: 4C6D05 JMP $056D ; if Y >= #$10 (past end of scramble LUT), bad password -- jump to exit ; above loop branches here when digit matches LUT entry 0000047B: 98 TYA ; put LUT index in A 0000047C: 8610 STX $10 ; dump digit index to $10 0000047E: 38 SEC 0000047F: E510 SBC $10 ; A now = scrambleLUT_index - passwordDigit_index 00000481: 290F AND #$0F ; take only the low 4 bits 00000483: 9D8007 STA $0780,X ; write back to password (now, this digit is descrambled) 00000486: 98 TYA ; LUT index back in A 00000487: 18 CLC 00000488: 6511 ADC $11 0000048A: 8511 STA $11 ; sum this with $11 0000048C: 2908 AND #$08 ; this is tricky. $11 is doubled 0000048E: C908 CMP #$08 ; but also, an additional 1 is added to it if bit 3 was set 00000490: 2611 ROL $11 ; This effectively makes $11 a 4-bit shifter 00000492: E8 INX ; inc our password digit index 00000493: E008 CPX #$08 ; see if we're done with the password -- note the last digit is not checked 00000495: D0D3 BNE $D3->046A ; if we're not done -- continue loop ; we reach here when the first 8 digits of the password have been run through the descrambler ; and the checksum has been built ; checksum for the password is stored at $11 ; it is compared to the last password digit 00000497: A511 LDA $11 ; get checksum 00000499: 290F AND #$0F ; use low 4 bits only 0000049B: CD8807 CMP $0788 ; compare it to last password digit (checksum digit) 0000049E: D016 BNE $16->04B6 ; if checksum does not match, jump to $04B6 (which jumps to BAD PASSWORD) ; password checksum is OK, further ensure that password is legit ; and record password data 000004A0: AD8107 LDA $0781 ; get digit 1 000004A3: 4A LSR A 000004A4: 4A LSR A ; / 4 000004A5: 2903 AND #$03 ; low 2 bits only 000004A7: C903 CMP #$03 ; both bits can't be set 000004A9: F00B BEQ $0B->04B6 ; if they are, BAD PASSWORD 000004AB: 8572 STA $72 ; store to ----> $72 <---- 000004AD: AD8007 LDA $0780 ; get digit 0 000004B0: F004 BEQ $04 ; if zero, BAD PASSWORD 000004B2: C907 CMP #$07 000004B4: 9003 BCC $03 ; if >= #$07... 000004B6: 4C6D05 JMP $056D ; BAD PASSWORD 000004B9: 8DFB07 STA $07FB ; store to ----> $07FB <---- (must be $01-06) 000004BC: A901 LDA #$01 000004BE: 8DFC07 STA $07FC ; $01->$07FC (???) ; do remaining digits (digits 7-2) 000004C1: A205 LDX #$05 000004C3: BD8207 LDA $0782,X ; get digit 000004C6: 2903 AND #$03 ; take low 2 bits of digit 000004C8: 0A ASL A ; left shift 2 000004C9: 0A ASL A 000004CA: 9D4306 STA $0643,X ; store to $0643,X 000004CD: BD8207 LDA $0782,X 000004D0: 4A LSR A 000004D1: 4A LSR A ; get bits 2-3 of digit 000004D2: 2903 AND #$03 ; right-shift 2 000004D4: 18 CLC 000004D5: 7DB4ED ADC $EDB4,X ; add to some LUT at $EDB4 (???) 000004D8: 9D5006 STA $0650,X ; store to $0650 and $0649 000004DB: 9D4906 STA $0649,X 000004DE: CA DEX 000004DF: 10E2 BPL $E2->04C3 ; dec loop counter, and continue loop ; here, password processing is pretty much done ; booya 000004E1: A900 LDA #$00 000004E3: 8530 STA $30 000004E5: A903 LDA #$03 ; <---- password is OK if you reached here 000004E7: A472 LDY $72 000004E9: F002 BEQ $02 000004EB: A902 LDA #$02 000004ED: 8D5806 STA $0658 000004F0: A983 LDA #$83 000004F2: 20D2C0 JSR $C0D2 000004F5: A91A LDA #$1A 000004F7: 20D2C0 JSR $C0D2 000004FA: A996 LDA #$96 000004FC: 850D STA $0D 000004FE: 2005C2 JSR $C205 00000501: 2026C5 JSR $C526 00000504: A900 LDA #$00 00000506: 8521 STA $21 00000508: 8523 STA $23 0000050A: 853B STA $3B 0000050C: A9C8 LDA #$C8 0000050E: 8520 STA $20 00000510: A9A7 LDA #$A7 00000512: 8522 STA $22 00000514: A50F LDA $0F 00000516: 18 CLC 00000517: 6903 ADC #$03 00000519: 201AC6 JSR $C61A 0000051C: A5B6 LDA $B6 0000051E: 2903 AND #$03 00000520: D00D BNE $0D 00000522: A50F LDA $0F 00000524: 18 CLC 00000525: 6901 ADC #$01 00000527: C903 CMP #$03 00000529: D002 BNE $02 0000052B: A900 LDA #$00 0000052D: 850F STA $0F 0000052F: A5E3 LDA $E3 00000531: 18 CLC 00000532: 6908 ADC #$08 00000534: 85E3 STA $E3 00000536: 9002 BCC $02 00000538: E6E4 INC $E4 0000053A: A5DC LDA $DC 0000053C: 8520 STA $20 0000053E: A5DD LDA $DD 00000540: 38 SEC 00000541: E5E3 SBC $E3 00000543: 85DD STA $DD 00000545: A5DE LDA $DE 00000547: E5E4 SBC $E4 00000549: 85DE STA $DE 0000054B: 8522 STA $22 0000054D: A5D5 LDA $D5 0000054F: E900 SBC #$00 00000551: 85D5 STA $D5 00000553: 8523 STA $23 00000555: A900 LDA #$00 00000557: 201AC6 JSR $C61A 0000055A: A5B6 LDA $B6 0000055C: 2901 AND #$01 0000055E: 18 CLC 0000055F: 6901 ADC #$01 00000561: 201AC6 JSR $C61A 00000564: C60D DEC $0D 00000566: D096 BNE $96 00000568: A905 LDA #$05 0000056A: 4C51C4 JMP $C451 ;; code jumps here when password is bad 0000056D: A99D LDA #$9D 0000056F: 20D2C0 JSR $C0D2 00000572: 4C0003 JMP $0300 ; $0575 -- descramble LUTs (four LUTs, each 16 bytes) $0575: 03 18 15 14 05 1C 0E 1E 17 0C 0A 02 00 0F 1A 08 $0585: 1A 1E 0C 03 09 14 05 1C 18 0E 08 0A 02 17 00 16 $0595: 0F 03 06 09 14 05 1C 0E 1E 17 0C 1A 08 00 0A 02 $05A5: 1E 17 0A 10 00 1A 0C 08 05 0F 03 18 09 14 1C 0E ; $05B5 -- special password LUT -- there are 5 passwords, each 9 digits (9 bytes) 1E 12 0E 13 1E 12 0E 13 1E 1E 18 0D 1C 1E 18 0D 1C 1E 1E 11 14 1E 1E 12 0A 0A 1E 1E 13 18 0B 1E 19 18 19 1E 1E 01 18 19 1E 14 0C 0E 1E ; ??? this is probably remnants of code that was previously in RAM here. ; does not appaear to be related to passwords 000005E2: A900 LDA #$00 000005E4: 8DFD07 STA $07FD 000005E7: 8DFE07 STA $07FE 000005EA: 8DFF07 STA $07FF 000005ED: A901 LDA #$01 000005EF: 8DFB07 STA $07FB 000005F2: 8DFC07 STA $07FC 000005F5: A900 LDA #$00 000005F7: 4C1C03 JMP $031C 000005FA: A577 LDA $77 000005FC: 29FE AND #$FE 000005FE: 48 .DB $48 000005FF: 20 .DB $20 ; LUT in ROM at $EDB4 $EDB4: 0D 0C 0A 0B 09 0E